using System;
using System.Collections.Generic;
using System.IO;
using System.Diagnostics.Contracts;
using System.Net.Sockets;
using Microsoft.Ccr.Core;
using System.Net;

namespace BitTorrent.Ccr.Adapters.IO
{
    public static class SocketAdapter
    {
        #region Receive

        public static PortSet<byte[], Exception> Receive(Socket socket, SocketFlags socketFlags, DispatcherQueue dispatcherQueue) {
            return Receive(socket, socketFlags, dispatcherQueue, new PortSet<byte[], Exception>());
        }

        public static PortSet<byte[], Exception> Receive(Socket socket, SocketFlags socketFlags, DispatcherQueue dispatcherQueue, PortSet<byte[], Exception> resultPort) {
            Contract.Requires(socket != null);
            Contract.Requires(resultPort != null);

            dispatcherQueue.Enqueue(new IterativeTask(() => DoReceive(socket, socketFlags, resultPort)));

            return resultPort;
        }

        private static IEnumerator<ITask> DoReceive(Socket socket, SocketFlags socketFlags, PortSet<byte[], Exception> resultPort) {
            byte[] buffer = new byte[0x10000];
            Port<IAsyncResult> receivePort = new Port<IAsyncResult>();
            using (MemoryStream stream = new MemoryStream()) {
                int read = 0;
                do {
                    try {
                        socket.BeginReceive(buffer, 0, buffer.Length, socketFlags, receivePort.Post, null);
                    } catch (Exception ex) {
                        resultPort.Post(ex);
                        yield break;
                    }

                    yield return receivePort.Receive();

                    try {
                        IAsyncResult r;
                        receivePort.Test(out r);
                        read = socket.EndReceive(r);
                    } catch (Exception ex) {
                        resultPort.Post(ex);
                        yield break;
                    }

                    stream.Write(buffer, 0, read);
                } while (read > 0);

                resultPort.Post(stream.ToArray());
            }
        }

        #endregion

        #region Send

        public static PortSet<SuccessResult, Exception> Send(Socket socket, byte[] buffer, SocketFlags socketFlags, DispatcherQueue dispatcherQueue) {
            return Send(socket, buffer, socketFlags, dispatcherQueue, new PortSet<SuccessResult, Exception>());
        }

        public static PortSet<SuccessResult, Exception> Send(Socket socket, byte[] buffer, SocketFlags socketFlags, DispatcherQueue dispatcherQueue, PortSet<SuccessResult, Exception> resultPort) {
            Contract.Requires(socket != null);
            Contract.Requires(buffer != null);
            Contract.Requires(dispatcherQueue != null);
            Contract.Requires(resultPort != null);

            dispatcherQueue.Enqueue(new IterativeTask(() => DoSend(socket, buffer, socketFlags, resultPort)));
            return resultPort;
        }

        private static IEnumerator<ITask> DoSend(Socket socket, byte[] buffer, SocketFlags socketFlags, PortSet<SuccessResult, Exception> resultPort) {

            Port<IAsyncResult> responsePort = new Port<IAsyncResult>();
            int sent = 0;

            do {
                try {
                    socket.BeginSend(buffer, sent, buffer.Length, socketFlags, responsePort.Post, null);
                } catch (Exception ex) {
                    resultPort.Post(ex);
                    yield break;
                }

                yield return responsePort.Receive();

                try {
                    IAsyncResult r;
                    responsePort.Test(out r);
                    sent += socket.EndSend(r);
                } catch (Exception ex) {
                    resultPort.Post(ex);
                    yield break;
                }
            } while (sent < buffer.Length);

            resultPort.Post(new SuccessResult());
            yield break;
        }

        #endregion

        #region Accept

        public static PortSet<Socket, Exception> Accept(int port, SocketType socketType, ProtocolType protocolType, int backlog, DispatcherQueue dispatcherQueue) {
            return Accept(port, socketType, protocolType, backlog, dispatcherQueue, new PortSet<Socket,Exception>());
        }

        public static PortSet<Socket, Exception> Accept(int port, SocketType socketType, ProtocolType protocolType, int backlog, DispatcherQueue dispatcherQueue, PortSet<Socket, Exception> resultPort) {
            // If the OS supports IPv6 we will have to sockets to accept from 
            var sockets = CreateInboundSocket(port, socketType, protocolType);
            foreach (var socket in sockets) {
                dispatcherQueue.Enqueue(new IterativeTask(() => DoAccept(socket, backlog, resultPort)));
            }
            return resultPort;
        }

        private static IEnumerator<ITask> DoAccept(Socket socket, int backlog, PortSet<Socket, Exception> resultPort) {
            Port<IAsyncResult> responsePort = new Port<IAsyncResult>();

            socket.Listen(backlog);
            while (true) {
                socket.BeginAccept(responsePort.Post, null);

                yield return responsePort.Receive();

                IAsyncResult r;
                responsePort.Test(out r);

                Socket result = socket.EndAccept(r);
                resultPort.Post(result);
            }
        }

        private static IEnumerable<Socket> CreateInboundSocket(int port, SocketType socketType, ProtocolType protocolType) {
            var sockets = new List<Socket>();
            var endpoint = new IPEndPoint(IPAddress.Any, port);

            var socket = new Socket(AddressFamily.InterNetwork, socketType, protocolType);
            socket.Bind(endpoint);
            socket.Blocking = false;
            socket.NoDelay = true;

            sockets.Add(socket);

            if (!Socket.OSSupportsIPv6)
                return sockets;

            endpoint = new IPEndPoint(IPAddress.IPv6Any, port);
            socket = new Socket(AddressFamily.InterNetworkV6, socketType, protocolType);
            socket.Bind(endpoint);
            socket.Blocking = false;
            socket.NoDelay = true;

            sockets.Add(socket);

            return sockets;
        }

        #endregion

        #region Connect

        public static PortSet<Socket, Exception> Connect(Socket socket, IPEndPoint endPoint, DispatcherQueue queue,
                                                 PortSet<Socket, Exception> resultPort) {
            queue.Enqueue(new IterativeTask(() => DoConnect(socket, endPoint, resultPort)));

            return resultPort;
        }

        public static PortSet<Socket, Exception> Connect(Socket socket, IPEndPoint endPoint, DispatcherQueue queue) {
            return Connect(socket, endPoint, queue, new PortSet<Socket, Exception>());
        }

        private static IEnumerator<ITask> DoConnect(Socket socket, EndPoint endPoint, PortSet<Socket, Exception> resultPort) {
            var responsePort = new Port<IAsyncResult>();

            try {
                socket.BeginConnect(endPoint, responsePort.Post, null);
            }
            catch (Exception e) {
                resultPort.Post(e);
                yield break;
            }

            yield return responsePort.Receive();

            IAsyncResult r;
            responsePort.Test(out r);

            try {
                socket.EndConnect(r);
            }
            catch (Exception e) {
                resultPort.Post(e);
            }

            resultPort.Post(socket);
        }

        #endregion
    }
}
